package org.thingsboard.server.dao.schedulerEvent;

import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.SchedulerEventId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;

import java.text.ParseException;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;

@Service
@Slf4j
public class SchedulerEventServiceImpl extends AbstractEntityService implements SchedulerEventService {
    public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
    public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
    public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
    public static final String INCORRECT_DEVICE_ID = "Incorrect schedulerEventId ";

    @Autowired
    private SchedulerEventDao schedulerEventDao;
    @Autowired
    private SchedulerEventInfoDao schedulerEventInfoDao;

    @Autowired
    private TenantDao tenantDao;

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    @Lazy
    private TbTenantProfileCache tenantProfileCache;

    @Override
    public SchedulerEvent findSchedulerEventById(SchedulerEventId schedulerEventId) {
        log.trace("Executing findDeviceById [{}]", schedulerEventId);
        validateId(schedulerEventId, INCORRECT_DEVICE_ID + schedulerEventId);
        return schedulerEventDao.findById(null, schedulerEventId.getId());
    }

    @Override
    public SchedulerEvent findSchedulerEventById(TenantId tenantId, SchedulerEventId schedulerEventId) {
        log.trace("Executing findDeviceById [{}]", schedulerEventId);
        validateId(schedulerEventId, INCORRECT_DEVICE_ID + schedulerEventId);
            return schedulerEventDao.findById(tenantId, schedulerEventId.getId());
    }

    @Override
    public SchedulerEventInfo findSchedulerEventInfoById(TenantId tenantId, SchedulerEventId schedulerEventId) {
        log.trace("Executing findDeviceById [{}]", schedulerEventId);
        validateId(schedulerEventId, INCORRECT_DEVICE_ID + schedulerEventId);
        return schedulerEventInfoDao.findById(tenantId, schedulerEventId.getId());
    }

    @Override
    public SchedulerEvent saveSchedulerEvent(SchedulerEvent schedulerEvent) {
        log.trace("Executing saveSchedulerEvent [{}]", schedulerEvent);
        schedulerEventValidator.validate(schedulerEvent, SchedulerEvent::getTenantId);
        return schedulerEventDao.save(schedulerEvent.getTenantId(), schedulerEvent);
    }

    @Override
    public SchedulerEventInfo saveSchedulerEventInfo(SchedulerEventInfo schedulerEventInfo) {
        log.trace("Executing saveSchedulerEvent [{}]", schedulerEventInfo);
        schedulerEventInfoValidator.validate(schedulerEventInfo, SchedulerEventInfo::getTenantId);
        return schedulerEventInfoDao.save(schedulerEventInfo.getTenantId(), schedulerEventInfo);
    }

    @Override
    public List<SchedulerEventInfo> findSchedulerEventInfosByStatus(SchedulerEventStatus status) {
        PageLink pageLink = new PageLink(Integer.MAX_VALUE, 0);;
        return schedulerEventInfoDao.findSchedulerEventInfosByStatus(status,pageLink);
    }

    @Override
    public ListenableFuture<List<EntitySubtype>> findSchedulerEventTypesByTenantId(TenantId tenantId) {
            log.trace("Executing findDeviceTypesByTenantId, tenantId [{}]", tenantId);
            validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
            ListenableFuture<List<EntitySubtype>> tenantSchedulerEventTypes = schedulerEventDao.findTenantSchedulerEventTypesAsync(tenantId.getId());
            return Futures.transform(tenantSchedulerEventTypes,
                    schedulerEventTypes -> {
                        schedulerEventTypes.sort(Comparator.comparing(EntitySubtype::getType));
                        return schedulerEventTypes;
                    }, MoreExecutors.directExecutor());

    }

    @Override
    public PageData<SchedulerEventInfo> findSchedulerEventInfosByTenantId(TenantId tenantId, PageLink pageLink) {
        log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink);
        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
        Validator.validatePageLink(pageLink);
        return schedulerEventInfoDao.findSchedulerEventInfosByTenantId(tenantId.getId(), pageLink);
    }

    @Override
    public PageData<SchedulerEventInfo> findSchedulerEventInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) {
        log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink);
        validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
        Validator.validateId(customerId, "Incorrect customerId " + customerId);
        Validator.validatePageLink(pageLink);
        return schedulerEventInfoDao. findSchedulerEventInfosByTenantIdAndCustomerId(tenantId.getId(),customerId.getId(), pageLink);
    }

    @Override
    public void deleteSchedulerEvent(TenantId tenantId, SchedulerEventId schedulerEventId) {
        log.trace("Executing deleteSchedulerEvent [{}]", schedulerEventId);
        Validator.validateId(schedulerEventId, INCORRECT_CUSTOMER_ID + schedulerEventId);
        deleteEntityRelations(tenantId, schedulerEventId);
        schedulerEventDao.removeById(tenantId, schedulerEventId.getId());
    }


    /**
     * 校验
     */
    private DataValidator<SchedulerEvent> schedulerEventValidator =
            new DataValidator<SchedulerEvent>() {
                @Override
                protected void validateCreate(TenantId tenantId, SchedulerEvent data) {
                    DefaultTenantProfileConfiguration profileConfiguration =
                            (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
                    //TODO
                    //可以在租户配置中限制定时任务的总任务
                    // long maxDashboards = profileConfiguration.getMaxDashboards();
                   // validateNumberOfEntitiesPerTenant(tenantId, schedulerEventDao, maxDashboards, EntityType.DASHBOARD);
                }

                //数据校验
                @Override
                protected void validateDataImpl(TenantId tenantId, SchedulerEvent schedulerEvent) {
                    if (StringUtils.isEmpty(schedulerEvent.getSchedule())) {
                        throw new DataValidationException("SchedulerEvent Cron should be specified!");
                    }
                    if (StringUtils.isEmpty(schedulerEvent.getName())) {
                        throw new DataValidationException("SchedulerEvent Name should be specified!");
                    }
                    try {
                        CronExpression cron = new CronExpression(schedulerEvent.getSchedule());
                        Date next= cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
                        if(next==null){
                            throw new DataValidationException("SchedulerEvent  cron is  expire!");
                        }

                    }catch (ParseException e){
                        throw new DataValidationException("SchedulerEvent Invalid cron expression!");
                    }

                    if (schedulerEvent.getStatus()== null) {
                        throw new DataValidationException("SchedulerEvent Status should be specified!");
                    }
                    if (schedulerEvent.getTenantId() == null) {
                        throw new DataValidationException("SchedulerEvent should be assigned to tenant!");
                    } else {
                        Tenant tenant = tenantDao.findById(tenantId, schedulerEvent.getTenantId().getId());
                        if (tenant == null) {
                            throw new DataValidationException("SchedulerEvent is referencing to non-existent tenant!");
                        }
                    }
                    if (schedulerEvent.getCustomerId() == null) {
                        schedulerEvent.setCustomerId(new CustomerId(NULL_UUID));
                    } else if (!schedulerEvent.getCustomerId().getId().equals(NULL_UUID)) {
                        Customer customer = customerDao.findById(schedulerEvent.getTenantId(), schedulerEvent.getCustomerId().getId());
                        if (customer == null) {
                            throw new DataValidationException("Can't assign device to non-existent customer!");
                        }
                        if (!customer.getTenantId().getId().equals(schedulerEvent.getTenantId().getId())) {
                            throw new DataValidationException("Can't assign device to customer from different tenant!");
                        }
                    }

                }
            };


    private DataValidator<SchedulerEventInfo> schedulerEventInfoValidator =
            new DataValidator<SchedulerEventInfo>() {
                @Override
                protected void validateCreate(TenantId tenantId, SchedulerEventInfo data) {
                    DefaultTenantProfileConfiguration profileConfiguration =
                            (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
                    //TODO
                    //可以在租户配置中限制定时任务的总任务
                    // long maxDashboards = profileConfiguration.getMaxDashboards();
                    // validateNumberOfEntitiesPerTenant(tenantId, schedulerEventDao, maxDashboards, EntityType.DASHBOARD);
                }
                //数据校验
                @Override
                protected void validateDataImpl(TenantId tenantId, SchedulerEventInfo schedulerEvent) {
                    if (StringUtils.isEmpty(schedulerEvent.getSchedule())) {
                        throw new DataValidationException("SchedulerEvent Cron should be specified!");
                    }
                    if (StringUtils.isEmpty(schedulerEvent.getName())) {
                        throw new DataValidationException("SchedulerEvent Name should be specified!");
                    }

                    if (schedulerEvent.getStatus()== null) {
                        throw new DataValidationException("SchedulerEvent Status should be specified!");
                    }
                    try {
                        CronExpression cron = new CronExpression(schedulerEvent.getSchedule());
                        Date next= cron.getNextValidTimeAfter(new Date(System.currentTimeMillis()));
                        if(next==null){
                            throw new DataValidationException("SchedulerEvent  cron is  expire!");
                        }

                    }catch (ParseException e){
                        throw new DataValidationException("SchedulerEvent Invalid cron expression!");
                    }
                    if (schedulerEvent.getTenantId() == null) {
                        throw new DataValidationException("SchedulerEvent should be assigned to tenant!");
                    } else {
                        Tenant tenant = tenantDao.findById(tenantId, schedulerEvent.getTenantId().getId());
                        if (tenant == null) {
                            throw new DataValidationException("SchedulerEvent is referencing to non-existent tenant!");
                        }
                    }
                    if (schedulerEvent.getCustomerId() == null) {
                        schedulerEvent.setCustomerId(new CustomerId(NULL_UUID));
                    } else if (!schedulerEvent.getCustomerId().getId().equals(NULL_UUID)) {
                        Customer customer = customerDao.findById(schedulerEvent.getTenantId(), schedulerEvent.getCustomerId().getId());
                        if (customer == null) {
                            throw new DataValidationException("Can't assign device to non-existent customer!");
                        }
                        if (!customer.getTenantId().getId().equals(schedulerEvent.getTenantId().getId())) {
                            throw new DataValidationException("Can't assign device to customer from different tenant!");
                        }
                    }
                }
            };
}
